/*
*  文件名称：mainwindows.cpp
*  项目：超声波流量表无线抄表系统（上位机程序）
*  开发环境:Qt 5.14.2 +  MSVC2017
*  李志坚、张国栋、陈昊然、高华阳、唐海森、张暄、李铠铮
*  2024山东大学（威海）嵌入式开发实训小组
*  2024-10-18
*  开源地址：https://gitee.com/sduwh-embedded-training-group/wireless-ultrasonic-flowmeter-monitoring-system
*/

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QTimer>
#include <QTime>
#include <QSerialPortInfo>
#include <QDebug>
#include <QMessageBox>
#include "DelayThread.h"

//相关标志位
//int If_start_search = NO;
//int If_finish_handshake = NO;
int If_start_View = NO;
int Restart_flag = NO;

//设备数组,用于设备检索,最多支持十个，检索到设备元素置1
int search_array[10] = {0};
int temp_device = 0;

//构造函数
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    // 初始化串口
    serialPort = new QSerialPort(this);

    // 初始化图标
    T_Chart_Init();
    Q_Chart_Init();

    // 初始化定时器
    timer_DrawLine = new QTimer();
    connect(timer_DrawLine, SIGNAL(timeout()), this, SLOT(DrawLine()));
    timer_DrawLine->start(200);   //开启绘图定时器
}

//析构函数
MainWindow::~MainWindow()
{
    delete ui;
}

//  初始化流量显示的表格
void MainWindow::Q_Chart_Init()
{
    //初始化QChart的实例
    Q_chart = new QChart();
    //初始化QSplineSeries的实例
    Q_lineSeries = new QSplineSeries();
    //设置曲线的名称
    Q_lineSeries->setName("流量曲线");
    //把曲线添加到QChart的实例chart中
    Q_chart->addSeries(Q_lineSeries);
    //声明并初始化X轴、两个Y轴
    QValueAxis *axisX = new QValueAxis();
    QValueAxis *axisY = new QValueAxis();
    //设置坐标轴显示的范围
    axisX->setMin(0);
    axisX->setMax(QUANTITY_TIME_MAX);
    axisY->setMin(QUANTITY_MIN);
    axisY->setMax(QUANTITY_MAX);
    //设置坐标轴上的格点
    axisX->setTickCount(5);
    axisY->setTickCount(1);
    //设置坐标轴显示的名称
    QFont font("Microsoft YaHei",8,QFont::Normal);//微软雅黑。字体大小8
    axisX->setTitleFont(font);
    axisY->setTitleFont(font);
    axisX->setTitleText("time");
    axisY->setTitleText("quantity of flow");
    //设置网格不显示
    axisY->setGridLineVisible(false);
    //下方：Qt::AlignBottom，左边：Qt::AlignLeft
    //右边：Qt::AlignRight，上方：Qt::AlignTop
    Q_chart->addAxis(axisX, Qt::AlignBottom);
    Q_chart->addAxis(axisY, Qt::AlignLeft);
    //把曲线关联到坐标轴
    Q_lineSeries->attachAxis(axisX);
    Q_lineSeries->attachAxis(axisY);
    //把chart显示到窗口上
    ui->Flow_View->setChart(Q_chart);
    ui->Flow_View->setRenderHint(QPainter::Antialiasing);      // 设置渲染：抗锯齿，如果不设置那么曲线就显得不平滑

}

//  初始化温度显示的表格
void MainWindow::T_Chart_Init()
{
    //初始化QChart的实例
    chart = new QChart();
    //初始化QSplineSeries的实例
    lineSeries = new QSplineSeries();
    //设置曲线的名称
    lineSeries->setName("温度曲线");
    //把曲线添加到QChart的实例chart中
    chart->addSeries(lineSeries);
    //声明并初始化X轴、两个Y轴
    QValueAxis *axisX = new QValueAxis();
    QValueAxis *axisY = new QValueAxis();
    //设置坐标轴显示的范围
    axisX->setMin(0);
    axisX->setMax(TEMPERATURE_TIME_MAX);
    axisY->setMin(TEMPERATURE_MIN);
    axisY->setMax(TEMPERATURE_MAX);
    //设置坐标轴上的格点
    axisX->setTickCount(5);
    axisY->setTickCount(1);
    //设置坐标轴显示的名称
    QFont font("Microsoft YaHei",8,QFont::Normal);//微软雅黑。字体大小8
    axisX->setTitleFont(font);
    axisY->setTitleFont(font);
    axisX->setTitleText("time");
    axisY->setTitleText("Temputure");
    //设置网格不显示
    axisY->setGridLineVisible(false);
    //下方：Qt::AlignBottom，左边：Qt::AlignLeft
    //右边：Qt::AlignRight，上方：Qt::AlignTop
    chart->addAxis(axisX, Qt::AlignBottom);
    chart->addAxis(axisY, Qt::AlignLeft);
    //把曲线关联到坐标轴
    lineSeries->attachAxis(axisX);
    lineSeries->attachAxis(axisY);
    //把chart显示到窗口上
    ui->graphicsView->setChart(chart);
    ui->graphicsView->setRenderHint(QPainter::Antialiasing);      // 设置渲染：抗锯齿，如果不设置那么曲线就显得不平滑
}

//在表格里绘制曲线，更新显示,定时器槽函数
void MainWindow::DrawLine()
{
    static int count = 0;
    static int Q_count = 0;
    if(If_start_View == YES)
    {
        if(Restart_flag == YES)
        {
            Restart_flag = NO;
            count = 0;
            Q_count = 0;
        }
        //update temputure

        if(count > TEMPERATURE_TIME_MAX)
        {
            //当曲线上最早的点超出X轴的范围时，剔除最早的点，
            lineSeries->removePoints(0,lineSeries->count() - TEMPERATURE_TIME_MAX);
            // 更新X轴的范围
            chart->axisX()->setMin(count - TEMPERATURE_TIME_MAX);
            chart->axisX()->setMax(count);
        }
        //增加新的点到曲线末端
        //float temp = rand()%40;
        float temp = SeriesDataPacket.Current_T;
        ui->current_T->setText(QString::number(temp));
        lineSeries->append(count,temp);//随机生成0到65的随机数
        count ++;

        //update quantity of flow

        if(Q_count > TEMPERATURE_TIME_MAX)
        {
            //当曲线上最早的点超出X轴的范围时，剔除最早的点，
            Q_lineSeries->removePoints(0,Q_lineSeries->count() - TEMPERATURE_TIME_MAX);
            // 更新X轴的范围
            Q_chart->axisX()->setMin(Q_count - TEMPERATURE_TIME_MAX);
            Q_chart->axisX()->setMax(Q_count);
        }
        //增加新的点到曲线末端
        //float temp_Q = rand()%40;
        float temp_Q = SeriesDataPacket.Current_Q;
        ui->current_Q->setText(QString::number(temp_Q));
        Q_lineSeries->append(Q_count,temp_Q);//随机生成0到65的随机数
        Q_count ++;
    }

}


void MainWindow::Series_Search_Init()
{
    //清空
    ui->portsCmb->clear();
    //搜索填充
    foreach(const QSerialPortInfo &info,QSerialPortInfo::availablePorts())
    {
        ui->portsCmb->addItem(info.portName());
    }
}

//搜索串口按键槽函数
void MainWindow::on_searchPort_clicked()
{
    Series_Search_Init();
}

//打开串口按键槽函数
void MainWindow::on_openPort_clicked()
{
    serialPort->setPortName(ui->portsCmb->currentText());
    if(serialPort->open(QIODevice::ReadWrite))
    {
        //qDebug() << "yes";
        ui->PortSelect->setText("串口打开成功");
        //设置参数
        serialPort->setBaudRate(SERIAL_BUAD_FRE);
        serialPort->setDataBits(QSerialPort::Data8);
        serialPort->setParity(QSerialPort::NoParity);
        serialPort->setStopBits(QSerialPort::OneStop);
        serialPort->setFlowControl(QSerialPort::NoFlowControl);
        //连接串口接收槽函数
        QObject::connect(serialPort,&QSerialPort::readyRead,this,&MainWindow::ReadSerialData);
        //串口接收状态机准备
        SerialReadStateMachine = NO_STATE;
    }
    else
    {
         //qDebug() << "no";
         ui->PortSelect->setText("串口打开失败");
    }
}

//串口接收槽函数
void MainWindow::ReadSerialData()
{
     // 从串口读取可用的数据
     QByteArray data = serialPort->readAll();
     // 将数据转换为字符串并输出调试信息
     //QString receivedData = QString::fromUtf8(data);
     //qDebug() << receivedData;
     int device_num = 0;
     switch(SerialReadStateMachine)
     {
        case NO_STATE:
            break;
        case SEARCH_STATE:
            device_num = analys_search_code_packet(data);
            if(device_num != -1)
            {
                // 查询到此设备
                search_array[device_num] = 1;
            }
            break;
        case HANDSHAKE_STATE:
            //等待握手回复
            if(if_connect_device(data) == YES)
            {
                current_device = temp_device;  //握手成功，临时设备转正
                SerialReadStateMachine = MONITOR_STATE;
                ui->startInform->setText(" 连接成功!!");
            }
            break;
        case MONITOR_STATE:
                 if(decodeSeriesDataPacket(data) == YES)//数据帧的解码
                 {
                     If_start_View = YES;
                 }

            break;
        default:
            break;
     }



}

//串口发送函数
void MainWindow::SendSerialData(const QByteArray &data)
{
     if(serialPort->isOpen() && serialPort->isWritable())
     {
         serialPort->write(data);
     }
}

//发送寻找设备的指令
void MainWindow::send_search_all_device_code()
{
    //发送寻找设备的指令
    QByteArray PacketofSearch;
    PacketofSearch.append(static_cast<char>(0X73)); //s
    PacketofSearch.append(static_cast<char>(0X65)); //e
    PacketofSearch.append(static_cast<char>(0X61)); //a
    PacketofSearch.append(static_cast<char>(0X72)); //r
    PacketofSearch.append(static_cast<char>(0X63)); //c
    PacketofSearch.append(static_cast<char>(0X68)); //h
    SendSerialData(PacketofSearch);
}
//解析查询回码
int MainWindow::analys_search_code_packet(const QByteArray &data)
{
    // 检查数据包长度是否足够
    if (data.size() < 4)
    {
        return -1; // 返回-1表示数据包不完整
    }

    // 检查帧头和帧尾
    if (static_cast<uint8_t>(data[0]) == 0x66 && static_cast<uint8_t>(data[2]) == 0xFF && static_cast<uint8_t>(data[3]) == 0xFF)
    {
        // 提取序号
        uint8_t command = static_cast<uint8_t>(data[1]);

        // 检查序号范围并返回对应值
        if (command >= 1 && command <= 9)
        {
            return command; // 返回1到9
        }
    }
    return -1; // 返回-1表示未识别的数据包
}

//搜索设备按键按下槽函数
void MainWindow::on_findDevice_clicked()
{
    send_search_all_device_code();
    SerialReadStateMachine = SEARCH_STATE;
    ui->deviceCmb->clear();
    for(int i = 0;i <= 9;i++)
    {
        search_array[i] = 0;
    }
    ui->startInform->setText("         ");
    //等待一段时间查询（开一个循环线程）
    DelayThread *searchThread = new DelayThread();
    connect(searchThread, &DelayThread::threadFinished, this, &MainWindow::onfindDeviceFinished);
    searchThread->start();
}

// 查询完成后的处理函数
void MainWindow::onfindDeviceFinished()
{
    //结束查询
    SerialReadStateMachine =  NO_STATE;
    //搜索到的设备个数
    int search_result_num = 0;
    //将结果显示到UI
    for(int i = 1;i <= 9;i++)
    {
        //如果查询到了
        if(search_array[i] == 1)
        {
            search_result_num++;
            ui->deviceCmb->addItem(QString("水表 %1").arg(i));
        }
    }

    if(search_result_num == 0)
    {
        //弹窗提示没有搜索到
         QMessageBox::information(this, "搜索结果", "没有搜索到任何设备，请检查连接！");
    }
}

//先进行握手处理， 再进行监视
void MainWindow::on_startView_clicked()
{
    //确定所选择的设备序号地址
    QString selectedText = ui->deviceCmb->currentText();    // 获取当前选中的文本

    //解析文本以获取水表的数字部分
    bool ok;
    int number = selectedText.split(' ').last().toInt(&ok); // 获取最后一个分开的部分并转换为整数
    temp_device = number;                                // 暂时认为是临时设备
    if (ok && number >= 1 && number <= 9)
    {
        int selectDevice = 0x00 | number;
        //进入握手阶段
        SerialReadStateMachine = HANDSHAKE_STATE;
        //发送握手指令
        QByteArray PacketofHandshake;
        PacketofHandshake.append(static_cast<char>(0X68)); //h
        PacketofHandshake.append(static_cast<char>(0X65)); //e
        PacketofHandshake.append(static_cast<char>(0X6C)); //l
        PacketofHandshake.append(static_cast<char>(0X6C)); //l
        PacketofHandshake.append(static_cast<char>(0X6F)); //o
        PacketofHandshake.append(static_cast<char>(selectDevice));
        SendSerialData(PacketofHandshake);
    }
}

int MainWindow::if_connect_device(const QByteArray &data)
{
    // 检查数据包长度是否足够
    if (data.size() < 5)
    {
        return NO;
    }
    // 检查指令start
    if (static_cast<uint8_t>(data[0]) == 0x73 && static_cast<uint8_t>(data[1]) == 0x74 && static_cast<uint8_t>(data[2]) == 0x61&& static_cast<uint8_t>(data[3]) == 0x72 && static_cast<uint8_t>(data[4]) == 0x74)
    {
        return  YES;
    }
}

int MainWindow::decodeSeriesDataPacket(const QByteArray &data)
{
    // 检查数据包长度是否足够
    if (data.size() < 9)
    {
        return NO;
    }
    //解析包的指令
    if(static_cast<uint8_t>(data[0]) == 0x55 && static_cast<uint8_t>(data[6]) == 0xFF && static_cast<uint8_t>(data[7]) == 0xFF && static_cast<uint8_t>(data[8]) == 0xFF)
    {
        //将十六进制转化为 整数
        uint8_t temp = static_cast<uint8_t>(data[1]);
        if(temp == current_device)  // 连接设备正确的话
        {
            SeriesDataPacket.number = static_cast<uint8_t>(data[1]);
            SeriesDataPacket.Current_T = static_cast<uint8_t>(data[2]) + static_cast<uint8_t>(data[3]) * 0.01;
            SeriesDataPacket.Current_Q = static_cast<uint8_t>(data[4]) + static_cast<uint8_t>(data[5]) * 0.01;
            return YES;
        }
    }
    return NO;
}


void MainWindow::on_endView_clicked()
{
    //发送停止指令
    QByteArray PacketofEnd;
    PacketofEnd.append(static_cast<char>(0X65)); //e
    PacketofEnd.append(static_cast<char>(0X6E)); //n
    PacketofEnd.append(static_cast<char>(0X64)); //d
    PacketofEnd.append(static_cast<char>(current_device)); //序号
    SendSerialData(PacketofEnd);

    //进入握手阶段
    SerialReadStateMachine = HANDSHAKE_STATE;

    // 解除当前设备
    current_device = 0;

    //关闭绘制
    If_start_View =  NO;

    //重新初始化图表和显示
    T_Chart_Init();
    Q_Chart_Init();
    ui->current_Q->setText("    ");
    ui->current_T->setText("    ");
    for(int i = 0;i <= 9;i++)
    {
        search_array[i] = 0;
    }
    ui->startInform->setText("已断开连接!");
    Restart_flag = YES;

}
